from bs4 import BeautifulSoup
from ckeditor_uploader.fields import RichTextUploadingField
from django.contrib.auth import get_user_model
from django.db import models
from django.db.utils import IntegrityError
from django.urls import reverse

from .utils import resize_image

# 允许使用自定制用户模型 settings.AUTH_USER_MODEL
User = get_user_model()


class BaseModel(models.Model):
    """基类，抽象模型"""
    date_created = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
    date_updated = models.DateTimeField(verbose_name="更新时间", auto_now=True)
    # 逻辑删除
    is_deleted = models.BooleanField(verbose_name="是否删除", default=False)

    class Meta:
        abstract = True  # 抽象模型，数据库中不会创建表结构


def upload_location(instance, filename):
    """图片上传目录"""
    return f"blog/{instance.user.id}/{instance.title}-{filename}"


class Post(BaseModel):
    """文章模型"""

    title = models.CharField("标题", max_length=100)
    img = models.ImageField("标题图", upload_to=upload_location, null=True, blank=True)
    content = RichTextUploadingField("正文", config_name="ck_config")
    user = models.ForeignKey(User, verbose_name="作者", on_delete=models.CASCADE)
    slug = models.CharField("短标题", max_length=32, unique=True, blank=True)
    # models.SET_NULL如果文章类别被删除，则把类别置为空
    category = models.ForeignKey(
        "Category", verbose_name="类别", on_delete=models.SET_NULL, null=True, blank=True
    )
    tags = models.ManyToManyField(
        "Tag", verbose_name="标签", help_text="多个标签请使用空格分隔", blank=True
    )
    num_views = models.PositiveIntegerField("浏览量", default=0)
    num_likes = models.PositiveIntegerField("点赞数", default=0)
    num_comments = models.PositiveIntegerField("评论数", default=0)

    class Meta:
        # 在admin中显示中文
        verbose_name = verbose_name_plural = "文章"
        ordering = ("-date_created", "-id")

    def __str__(self):
        return self.title

    @property
    def excerpt(self, length=100):
        """文章摘要，不带HTML标签"""
        bs = BeautifulSoup(self.content, "html.parser")
        return bs.text[:length] + "..."

    def get_absolute_url(self):
        """获取详情页面URL"""
        return reverse("blog:detail", kwargs={"slug": self.slug})

    def get_edit_url(self):
        """获取编辑页面URL"""
        return reverse("blog:update", kwargs={"slug": self.slug})

    def get_delete_url(self):
        """获取删除页面URL"""
        return reverse("blog:delete", kwargs={"slug": self.slug})

    def save(self, *args, **kwargs):

        # 使用随机字符串作为slug，并且是唯一的，通常为12个字符(get_random_string)
        # 如果出现唯一性错误时，使用uuid生成唯一slug，因此这里设置slug字段最大长度为32个字符

        # 在信号(pre_save)中使用get_random_string函数生成12位的slug，如果出现重复，调用
        # save方法时会抛出IntegrityError（唯一性错误），这时使用uuid生成32位slug
        # 它的值永远是唯一的
        try:
            super().save(*args, **kwargs)
        except IntegrityError:
            import uuid

            self.slug = uuid.uuid4().hex
            super().save(*args, **kwargs)

        # 更改上传图片大小
        if self.img:  # 标题图片可能为空
            resize_image(self.img)


class Tag(BaseModel):
    """标签模型"""
    name = models.CharField(max_length=32, verbose_name="标签")
    user = models.ForeignKey(User, verbose_name="作者", on_delete=models.CASCADE)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = verbose_name_plural = "标签"
        ordering = ("name",)


class Category(BaseModel):
    """文章分类，栏目"""
    name = models.CharField("名称", max_length=64)
    # 使用随机字符串作为slug，并且是唯一的，通常为12个字符(get_random_string)
    # 如果出现唯一性错误时，使用uuid生成唯一slug，因此这里设置slug字段最大长度为32个字符
    # slug = models.CharField(max_length=32, unique=True, editable=False)
    # 新闻术语-短标题
    slug = models.CharField("短标题", max_length=32, unique=True, blank=True)
    description = models.TextField("描述", blank=True, null=True)

    # 是否出现在导航栏
    is_nav = models.BooleanField("是否导航", default=False)

    # models.CASCADE如果删除用户，那么他关联的类别也会被删除
    user = models.ForeignKey(User, verbose_name="作者", on_delete=models.CASCADE)

    class Meta:
        verbose_name = verbose_name_plural = "类别"
        # 联合唯一，同一个用户创建的文章类别不能相同
        unique_together = (("name", "user"),)

    def __str__(self):
        return f"{self.name}"

    def save(self, *args, **kwargs):

        # 在信号中使用get_random_string函数生成12位的slug，如果出现重复，调用
        # save方法时会抛出IntegrityError（唯一性错误），这时使用uuid生成32位slug
        # 它的值永远是唯一的
        try:
            super().save(*args, **kwargs)
        except IntegrityError:
            import uuid

            self.slug = uuid.uuid4().hex
            super().save(*args, **kwargs)


class Like(BaseModel):
    """点赞模型"""
    user = models.ForeignKey(User, verbose_name="用户", on_delete=models.CASCADE)
    post = models.ForeignKey(Post, verbose_name="文章", on_delete=models.CASCADE)

    class Meta:
        verbose_name = verbose_name_plural = "点赞"
        # 联合唯一 一个用户只能点赞一次同一篇文章
        unique_together = (("user", "post"),)

    def __str__(self):
        return f"{self.user!s}点赞{self.post!s}"
